/**
 * Copyright (c) 2018-2028, HQY 胡庆一 (215218393@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocm.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ocm.gateway.bo.ApiBO;
import com.ocm.gateway.exception.AuthException;
import com.ocm.gateway.props.AuthProperties;
import com.ocm.gateway.provider.AuthProvider;
import com.ocm.gateway.provider.ResponseProvider;
import com.ocm.gateway.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 鉴权认证
 *
 * @author HQY
 */
@Slf4j
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {

	private final AuthProperties authProperties;
	private final ObjectMapper objectMapper;
	private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

	@Resource
	private final OkHttpClient okHttpClient;

	@Resource
	private final DiscoveryClient discoveryClient;

	//所有api权限信息
	public static List<ApiBO> apiBOS;

	@SneakyThrows
	@PostConstruct
	public void init() {
		try {
			URI uri = discoveryClient.getInstances("ocm-system").get(0).getUri();
			Request request = new Request.Builder().url(uri+"/api/getAll").build();
			Response response = okHttpClient.newCall(request).execute();
			ResponseBody body = response.body();
			assert body != null;
			apiBOS = JSON.parseArray(body.string(), ApiBO.class);
		} catch (Exception e) {
			throw new Exception("获取接口权限失败，网关初始化失败,请检查服务是否开启");
		}
	}

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

		ServerHttpResponse resp = exchange.getResponse();
		try {
			isPass(exchange);
		}catch (AuthException e){
			return unAuth(e.getCode(),resp, e.getMessage(),e.getHttpStatus());
		}

		return chain.filter(exchange);
	}

	private boolean isSkip(String path) {
		return AuthProvider.getDefaultSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
			|| authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
	}

	private String isPass(ServerWebExchange exchange){
		//三种api类型 1.直接放行 2登录访问 3权限访问
		//1.直接放行 此放行为配置在本类及nacos配置文件中的url
		String path = exchange.getRequest().getURI().getPath();
		HttpMethod method = exchange.getRequest().getMethod();
		if (isSkip(path)) {
			return null;
		}
		for(ApiBO t:apiBOS){

			if(antPathMatcher.match(t.getApiPath(),path)){

				if(t.getApiType()==0){
					throw new AuthException(HttpStatus.FORBIDDEN, "无权访问",403);
				}

				if(StringUtils.isNotBlank(t.getApiMethod())) {
					assert method != null;
					if(!method.matches(t.getApiMethod())){
						throw new AuthException(HttpStatus.FORBIDDEN, "该地址此方法不允许",403);
					}
				}

				//1.直接放行 数据库中的api信息
				if(t.getApiType()==3){
					return null;
				}
				//登录访问 权限访问均需令牌 解析令牌 判断是否登录有效
				String headerToken = exchange.getRequest().getHeaders().getFirst(AuthProvider.AUTH_KEY);
				String paramToken = exchange.getRequest().getQueryParams().getFirst(AuthProvider.AUTH_KEY);
				if(StringUtils.isBlank(headerToken) && StringUtils.isBlank(paramToken))
					throw new AuthException(HttpStatus.UNAUTHORIZED, "缺失令牌,鉴权失败");
				String auth = StringUtils.isBlank(headerToken) ? paramToken : headerToken;
				String token = JwtUtil.getToken(auth);
				Claims claims = JwtUtil.parseJWT(token);
				if (claims == null) {
					throw new AuthException(HttpStatus.UNAUTHORIZED, "缺失令牌,鉴权失败");
				}

				//2.登录访问
				if(t.getApiType()==2){
					return null;
				}

				//3.权限访问
				if(t.getApiType()==1){
					String role_name = (String) claims.get("role_name");
					if(role_name.length()<1)
						throw new AuthException(HttpStatus.FORBIDDEN, "无权访问",403);
					for(String v : role_name.split(",")){
						if(t.getRoles().contains(v)){
							return null;
						}
					}
				}
				throw new AuthException(HttpStatus.FORBIDDEN, "无权访问",403);
			}
		}
		throw new AuthException(HttpStatus.NOT_FOUND, "未找到该地址",404);
	}

	private Mono<Void> unAuth(Integer code,ServerHttpResponse resp, String msg,HttpStatus httpStatus) {
		resp.setStatusCode(httpStatus);
		return getVoidMono(code,resp, msg);
	}


	private Mono<Void> getVoidMono(Integer code,ServerHttpResponse resp, String msg) {
		resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
		String result = "";
		try {
			if(code!=null){
				result = objectMapper.writeValueAsString(ResponseProvider.unAuth(code,msg));
			}else{
				result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
			}
		} catch (JsonProcessingException e) {
			log.error(e.getMessage(), e);
		}
		DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
		return resp.writeWith(Flux.just(buffer));
	}


	@Override
	public int getOrder() {
		return -100;
	}

}
